// FacebookRequest.cs
// Facebook/Framework/Service/Core
// Copyright (c) 2007, Nikhil Kothari. All Rights Reserved.
//

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;

namespace Facebook.Service.Core {

    /// <summary>
    /// Represents a request used to invoke a Facebook API method.
    /// </summary>
    public sealed class FacebookRequest {

        private const string FacebookHttpUri = "http://api.facebook.com/bestserver.php";
        private const string FacebookHttpsUri = "https://api.facebook.com/bestserver.php";

        private FacebookSession _session;
        private FacebookRequestParameterList _parameters;
        private WebRequest _asyncRequest;
        private string _asyncMethod;

        /// <summary>
        /// Creates an new instance of a request with the specified session.
        /// </summary>
        /// <param name="session">The session to use to issue the request.</param>
        public FacebookRequest(FacebookSession session) {
            if (session == null) {
                throw new ArgumentNullException("session");
            }

            _session = session;
            _parameters = new FacebookRequestParameterList();
        }

        /// <summary>
        /// Gets the collection of parameters associated with the request.
        /// </summary>
        public FacebookRequestParameterList Parameters {
            get {
                return _parameters;
            }
        }

        /// <summary>
        /// Starts an asynchronous call to create an authorization token. This corresponds to the
        /// Facebook auth.createToken method.
        /// </summary>
        /// <param name="callback">The async callback that is invoked when the request completes.</param>
        /// <param name="asyncState">The state to associate with the asynchronous call.</param>
        /// <returns>An async result that represents the asynchronous call.</returns>
        public IAsyncResult BeginCreateAuthToken(AsyncCallback callback, object asyncState) {
            if (_asyncRequest != null) {
                throw new InvalidOperationException("A method is currently being invoked using this request.");
            }

            _asyncRequest = CreateRequest(null, _session);
            return _asyncRequest.BeginGetResponse(callback, asyncState);
        }

        /// <summary>
        /// Starts an asynchronous call to create a session. This corresponds to the
        /// Facebook auth.getSession method.
        /// </summary>
        /// <param name="authToken">The authorization token to use to create the session.</param>
        /// <param name="callback">The async callback that is invoked when the request completes.</param>
        /// <param name="asyncState">The state to associate with the asynchronous call.</param>
        /// <returns>An async result that represents the asynchronous call.</returns>
        public IAsyncResult BeginCreateSession(string authToken, AsyncCallback callback, object asyncState) {
            if (String.IsNullOrEmpty(authToken)) {
                throw new ArgumentNullException("authToken");
            }
            if (_asyncRequest != null) {
                throw new InvalidOperationException("A method is currently being invoked using this request.");
            }

            _asyncRequest = CreateRequest(authToken, _session);
            return _asyncRequest.BeginGetResponse(callback, asyncState);
        }

        /// <summary>
        /// Starts an asynchronous call to invoke the specified method.
        /// </summary>
        /// <param name="method">The name of the API method to invoke.</param>
        /// <param name="callback">The async callback that is invoked when the request completes.</param>
        /// <param name="asyncState">The state to associate with the asynchronous call.</param>
        /// <returns>An async result that represents the asynchronous call.</returns>
        public IAsyncResult BeginInvokeMethod(string method, AsyncCallback callback, object asyncState) {
            if (_asyncRequest != null) {
                throw new InvalidOperationException("A method is currently being invoked using this request.");
            }

            _asyncMethod = method;
            _asyncRequest = CreateRequest(method, _session, _parameters);
            return _asyncRequest.BeginGetResponse(callback, asyncState);
        }

        private static HttpWebRequest CreateRequest(string authToken, FacebookSession session) {
            FacebookRequestParameterList parameters = new FacebookRequestParameterList();
            string method;
            string uri;

            if (String.IsNullOrEmpty(authToken)) {
                method = "auth.createToken";
                uri = FacebookHttpUri;
            }
            else {
                parameters["auth_token"] = authToken;

                method = "auth.getSession";
                uri = FacebookHttpsUri;
            }

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
            parameters.InitializeRequest(request, method, session, /* useSessionKey */ false);

            return request;
        }

        private static HttpWebRequest CreateRequest(string method, FacebookSession session, FacebookRequestParameterList parameters) {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(FacebookHttpUri);
            parameters.InitializeRequest(request, method, session, /* useSessionKey */ true);

            return request;
        }

        /// <summary>
        /// Creates an authorization token. This corresponds to the Facebook auth.createToken method.
        /// </summary>
        /// <returns>A new authorization token on success.</returns>
        public string CreateAuthToken() {
            string authToken = null;

            HttpWebRequest webRequest = CreateRequest(null, _session);
            HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

            if (webResponse.StatusCode == HttpStatusCode.OK) {
                FacebookResponse authTokenResponse = new FacebookResponse(webResponse.GetResponseStream(), null);
                if (authTokenResponse.IsError == false) {
                    authToken = authTokenResponse.Result as string;
                }
            }

            return authToken;
        }

        /// <summary>
        /// Creates a new session token. This corresponds to the Facebook auth.getSession method.
        /// </summary>
        /// <param name="authToken">The authorization token to use to create the session.</param>
        /// <param name="userID">The user ID associated with the session.</param>
        /// <param name="expires">Whether the created session expires or is long-lived.</param>
        /// <param name="secret">A new secret that should be used for this session if applicable.</param>
        /// <returns>A new session.</returns>
        public string CreateSession(string authToken, out string userID, out bool expires, out string secret) {
            userID = null;
            expires = true;
            secret = null;

            if (String.IsNullOrEmpty(authToken)) {
                throw new ArgumentNullException("authToken");
            }

            string sessionKey = null;

            HttpWebRequest webRequest = CreateRequest(authToken, _session);
            HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

            if (webResponse.StatusCode == HttpStatusCode.OK) {
                FacebookResponse sessionResponse = new FacebookResponse(webResponse.GetResponseStream(), null);
                if (sessionResponse.IsError == false) {
                    Hashtable result = sessionResponse.Result as Hashtable;
                    if (result != null) {
                        sessionKey = (string)result["session_key"];
                        userID = (string)result["uid"];
                        expires = ((int)result["expires"] == 0);
                        secret = (string)result["secret"];
                    }
                }
            }

            return sessionKey;
        }

        /// <summary>
        /// Completes an asynchronous call to create an authorization token.
        /// </summary>
        /// <param name="asyncResult">The async result from the corresponding BeginCreateAuthToken call.</param>
        /// <returns>A new authorization token on success.</returns>
        public string EndCreateAuthToken(IAsyncResult asyncResult) {
            if (asyncResult == null) {
                throw new ArgumentNullException("asyncResult");
            }
            if (_asyncRequest == null) {
                throw new InvalidOperationException("No method is currently being invoked using this request.");
            }

            string authToken = null;

            try {
                HttpWebResponse webResponse = (HttpWebResponse)_asyncRequest.EndGetResponse(asyncResult);

                if (webResponse.StatusCode == HttpStatusCode.OK) {
                    FacebookResponse authTokenResponse = new FacebookResponse(webResponse.GetResponseStream(), null);
                    if (authTokenResponse.IsError == false) {
                        authToken = authTokenResponse.Result as string;
                    }
                }
            }
            finally {
                _asyncRequest = null;
            }

            return authToken;
        }

        /// <summary>
        /// Completes an asynchronous call to create a new session.
        /// </summary>
        /// <param name="asyncResult">The async result from the corresponding BeginCreateSession call.</param>
        /// <param name="userID">The user ID associated with the session.</param>
        /// <param name="expires">Whether the created session expires or is long-lived.</param>
        /// <param name="secret">A new secret that should be used for this session if applicable.</param>
        /// <returns>A new session.</returns>
        public string EndCreateSession(IAsyncResult asyncResult, out string userID, out bool expires, out string secret) {
            userID = null;
            expires = true;
            secret = null;

            if (asyncResult == null) {
                throw new ArgumentNullException("asyncResult");
            }
            if (_asyncRequest == null) {
                throw new InvalidOperationException("No method is currently being invoked using this request.");
            }

            string sessionKey = null;

            try {
                HttpWebResponse webResponse = (HttpWebResponse)_asyncRequest.EndGetResponse(asyncResult);

                if (webResponse.StatusCode == HttpStatusCode.OK) {
                    FacebookResponse sessionResponse = new FacebookResponse(webResponse.GetResponseStream(), null);
                    if (sessionResponse.IsError == false) {
                        Hashtable result = sessionResponse.Result as Hashtable;
                        if (result != null) {
                            sessionKey = (string)result["session_key"];
                            userID = (string)result["uid"];
                            expires = ((int)result["expires"] == 0);
                            secret = (string)result["secret"];
                        }
                    }
                }
            }
            finally {
                _asyncRequest = null;
            }

            return sessionKey;
        }

        /// <summary>
        /// Completes an asynchronous call to invoke an API method.
        /// </summary>
        /// <param name="asyncResult">The async result from the corresponding BeginCreateSession call.</param>
        /// <returns>The resulting response.</returns>
        public FacebookResponse EndInvokeMethod(IAsyncResult asyncResult) {
            if (_asyncRequest == null) {
                throw new InvalidOperationException("No method is currently being invoked using this request.");
            }

            FacebookResponse response = null;

            try {
                HttpWebResponse webResponse = (HttpWebResponse)_asyncRequest.EndGetResponse(asyncResult);
                if (webResponse.StatusCode != HttpStatusCode.OK) {
                    response = new FacebookResponse(webResponse.StatusCode, _asyncMethod);
                }
                else {
                    Stream responseStream = webResponse.GetResponseStream();
                    response = new FacebookResponse(responseStream, _asyncMethod);
                }
            }
            finally {
                _asyncRequest = null;
                _asyncMethod = null;
            }

            _session.LogResponse(response);
            return response;
        }

        /// <summary>
        /// Invokes the specified API method.
        /// </summary>
        /// <param name="method">The name of the API method to invoke.</param>
        /// <returns>The resulting response.</returns>
        public FacebookResponse InvokeMethod(string method) {
            HttpWebRequest webRequest = CreateRequest(method, _session, _parameters);

            FacebookResponse response = null;

            HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
            if (webResponse.StatusCode != HttpStatusCode.OK) {
                response = new FacebookResponse(webResponse.StatusCode, method);
            }
            else {
                Stream responseStream = webResponse.GetResponseStream();
                response = new FacebookResponse(responseStream, method);
            }

            _session.LogResponse(response);
            return response;
        }
    }
}
